﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.CodeDom;
using System.Reflection;

namespace CodeDomHelper.CodeDom
{

    public enum ClassVisibility
    {
        Private,
        Internal,
        Public
    }

    public enum MemberVisibility
    {
        Private,
        Internal,
        Protected,
        InternalProtected,
        Public
    }

    public static partial class Helper
    {

        #region Field

        public static FieldMember CreateField(this CodeTypeMemberCollection @class, string fieldName, MemberVisibility fieldVisibility, bool isVirtual, TypeExp type)
        {
            var i = CreateField(fieldName, fieldVisibility, isVirtual, type);
            @class.Add(i);
            return i;
        }

        public static FieldMember CreateFieldAutoInit(this CodeTypeMemberCollection @class, string fieldName, MemberVisibility fieldVisibility, bool isVirtual, TypeExp type, params CodeExpression[] parameters)
        {
            var i = CreateField(fieldName, fieldVisibility, isVirtual, type);

            if (parameters != null && parameters.Length > 0)
                i.InitExpression = i.New(parameters);
            else
                i.InitExpression = i.New();

            @class.Add(i);
            return i;
        }

        public static FieldMember CreateFielWithInit(this CodeTypeMemberCollection @class, string fieldName, MemberVisibility fieldVisibility, bool isVirtual, TypeExp type, CodeExpression parameters)
        {
            var i = CreateField(fieldName, fieldVisibility, isVirtual, type);
            i.InitExpression = parameters;
            @class.Add(i);
            return i;
        }


        private static FieldMember CreateField(string fieldName, MemberVisibility fieldVisibility, bool isVirtual, TypeExp type)
        {

            var i = new FieldMember(fieldName, type);

            switch (fieldVisibility)
            {
                case MemberVisibility.Private:
                    i.Attributes = MemberAttributes.Private;
                    break;
                case MemberVisibility.Internal:
                    i.Attributes = MemberAttributes.Family;
                    break;
                case MemberVisibility.Protected:
                    i.Attributes = MemberAttributes.FamilyAndAssembly;
                    break;
                case MemberVisibility.InternalProtected:
                    i.Attributes = MemberAttributes.FamilyOrAssembly;
                    break;
                case MemberVisibility.Public:
                    i.Attributes = MemberAttributes.Public;
                    break;
                default:
                    break;
            }

            if (!isVirtual)
                i.Attributes |= MemberAttributes.Final;

            return i;
        }


        #endregion

        #region Property


        public static PropertyMember CreatePropertyWithPrivateField(this TypeMember @class, string propertyName, MemberVisibility methodVisibility, TypeExp type)
        {
            return CreatePropertyWithPrivateField(@class, propertyName, methodVisibility, false, type);
        }

        public static PropertyMember CreatePropertyWithPrivateField(this TypeMember @class, string propertyName, MemberVisibility methodVisibility, bool isVirtual, TypeExp type)
        {
            FieldMember field = @class.Members.CreateField("_" + propertyName, MemberVisibility.Private, false, type);
            var i = CreateProperty(@class.Members, propertyName, methodVisibility, isVirtual, field);
            return i;
        }

        public static PropertyMember CreateProperty(this CodeTypeMemberCollection type, string propertyName, MemberVisibility methodVisibility, bool isVirtual, FieldMember privatefield)
        {
            var i = CreateProperty(propertyName, methodVisibility, isVirtual, privatefield);
            type.Add(i);
            return i;
        }

        public static PropertyMember CreateProperty(this CodeTypeMemberCollection type, string propertyName, MemberVisibility methodVisibility, FieldMember privatefield)
        {
            var i = CreateProperty(propertyName, methodVisibility, false, privatefield);
            type.Add(i);
            return i;
        }

        
        public static PropertyMember CreateProperty(string propertyName, MemberVisibility methodVisibility, FieldMember privatefield)
        {
            return CreateProperty(propertyName, methodVisibility, false, privatefield);
        }

        public static PropertyMember CreateProperty(string propertyName, MemberVisibility methodVisibility, bool isVirtual, FieldMember privatefield)
        {

            var i = CreateProperty(propertyName, methodVisibility, isVirtual, privatefield.Type);

            FieldExp f = new FieldExp(Helper.This(), privatefield.Name, privatefield.Type);
            i.GetStatements.Return(f);
            i.SetStatements.Set(f, new CodePropertySetValueReferenceExpression());

            return i;
        }
        private static PropertyMember CreateProperty(string propertyName, MemberVisibility fieldVisibility, bool isVirtual, TypeExp type)
        {

            var i = new PropertyMember(propertyName, type);

            switch (fieldVisibility)
            {
                case MemberVisibility.Private:
                    i.Attributes = MemberAttributes.Private;
                    break;
                case MemberVisibility.Internal:
                    i.Attributes = MemberAttributes.Family;
                    break;
                case MemberVisibility.Protected:
                    i.Attributes = MemberAttributes.FamilyAndAssembly;
                    break;
                case MemberVisibility.InternalProtected:
                    i.Attributes = MemberAttributes.FamilyOrAssembly;
                    break;
                case MemberVisibility.Public:
                    i.Attributes = MemberAttributes.Public;
                    break;
                default:
                    break;
            }

            if (!isVirtual)
                i.Attributes |= MemberAttributes.Final;


            return i;
        }


        #endregion

        #region Method


        public static MethodMember CreateMethod(this CodeTypeMemberCollection type, string methodName, MemberVisibility methodVisibility, bool isVirtual, TypeExp returnType, params ParameterExp[] parameters)
        {
            var i = CreateMethod(methodName, methodVisibility, isVirtual, returnType, parameters);
            type.Add(i);
            return i;
        }
        public static MethodMember CreateMethod(string methodName, MemberVisibility methodVisibility, bool isVirtual, TypeExp returnType, params ParameterExp[] parameters)
        {
            var i = CreateMethod(methodName, methodVisibility, isVirtual, parameters);

            //Type t = returnType.TypeExpression.Evaluate();

            i.ReturnType = new CodeTypeReference(returnType.TypeExpression.AssemblyQualifiedName);
           
            return i;
        }


        public static MethodMember CreateMethod(this CodeTypeMemberCollection type, string methodName, MemberVisibility methodVisibility, bool isVirtual, CodeStatement[] codes, TypeExp returnType, params ParameterExp[] parameters)
        {
            var i = CreateMethod(methodName, methodVisibility, isVirtual, codes, returnType, parameters);
            type.Add(i);
            return i;
        }

        public static MethodMember CreateMethod(string methodName, MemberVisibility methodVisibility, bool isVirtual, CodeStatement[] codes, TypeExp returnType, params ParameterExp[] parameters)
        {
            var i = CreateMethod(methodName, methodVisibility, isVirtual, parameters);

            //Type t = returnType.TypeExpression.Evaluate();

            i.ReturnType = new CodeTypeReference(returnType.TypeExpression.AssemblyQualifiedName);

            i.Statements.AddRange(codes);
            return i;
        }


        public static MethodMember CreateMethod(this CodeTypeMemberCollection type, string methodName, MemberVisibility methodVisibility, bool isVirtual, CodeStatement[] codes, params ParameterExp[] parameters)
        {
            var i = CreateMethod(methodName, methodVisibility, isVirtual, codes, parameters);
            type.Add(i);
            return i;
        }
        public static MethodMember CreateMethod(string methodName, MemberVisibility methodVisibility, bool isVirtual, CodeStatement[] codes, params ParameterExp[] parameters)
        {
            var i = CreateMethod(methodName, methodVisibility, isVirtual, parameters);
            i.Statements.AddRange(codes);
            return i;
        }


        public static MethodMember CreateMethod(this CodeTypeMemberCollection type, string methodName, MemberVisibility methodVisibility, bool isVirtual, params ParameterExp[] parameters)
        {
            var i = CreateMethod(methodName, methodVisibility, isVirtual, parameters);
            type.Add(i);
            return i;
        }


        public static MethodMember CreateMethod(string methodName, MemberVisibility methodVisibility, bool isVirtual, params ParameterExp[] parameters)
        {
            MethodMember method = new MethodMember(methodName);

            switch (methodVisibility)
            {
                case MemberVisibility.Private:
                    method.Attributes = MemberAttributes.Private;
                    break;
                case MemberVisibility.Internal:
                    method.Attributes = MemberAttributes.Family;
                    break;
                case MemberVisibility.Protected:
                    method.Attributes = MemberAttributes.FamilyAndAssembly;                    
                    break;
                case MemberVisibility.InternalProtected:
                    method.Attributes = MemberAttributes.FamilyOrAssembly;
                    break;
                case MemberVisibility.Public:
                    method.Attributes = MemberAttributes.Public;
                    break;
                default:
                    break;
            }

            if (!isVirtual)
                method.Attributes |= MemberAttributes.Final;

            if (parameters != null && parameters.Length != 0)
                method.Parameters.AddRange(parameters);

            return method;
        }


        public static T Static<T>(this T method)
            where T : CodeTypeMember
        {
            method.Attributes |= MemberAttributes.Static;
            return method;
        }

        public static T Abstract<T>(this T method)
            where T : CodeTypeMember
        {
            method.Attributes |= MemberAttributes.Abstract;
            return method;
        }

        public static T Sealed<T>(this T method)
            where T : CodeTypeMember
        {
            method.Attributes |= MemberAttributes.Final;
            return method;
        }

        public static T New<T>(this T method)
            where T : CodeTypeMember
        {
            method.Attributes |= MemberAttributes.New;
            return method;
        }

        public static T Override<T>(this T method)
            where T : CodeTypeMember
        {
            method.Attributes |= MemberAttributes.Override;
            return method;
        }

        public static T Overloaded<T>(this T method)
            where T : CodeTypeMember
        {
            method.Attributes |= MemberAttributes.Overloaded;
            return method;
        }

        #endregion

        #region Attribute


        public static void CreateAttribute(this MethodMember method, Type type)
        {
            var t = new TypeExp(type);

            CreateAttribute(method, t, null);

        }


        public static void CreateAttribute(this CodeTypeMember method, TypeExp type,params CodeAttributeArgument[] parameters)
        {
            var attr = new CodeAttributeDeclaration(type);

            if (parameters != null && parameters.Length > 0)
                attr.Arguments.AddRange(parameters);

            method.CustomAttributes.Add(attr);
        }

        public static void CreateAttribute(this CodeTypeMember method, Type type, params CodeAttributeArgument[] parameters)
        {
            var attr = new CodeAttributeDeclaration(new CodeTypeReference(type));

            if (parameters != null && parameters.Length > 0)
                attr.Arguments.AddRange(parameters);

            method.CustomAttributes.Add(attr);
        }


        public static void CreateAttribute(this MethodMember method, Type type, object parametersNotNamed)
        {

            var t = new TypeExp(type);

            List<CodeAttributeArgument> lst = new List<CodeAttributeArgument>();

            if (parametersNotNamed != null)
                foreach (var i in parametersNotNamed.GetType().GetProperties())
                {
                    var arg = new CodeAttributeArgument(GetValue(parametersNotNamed, i) as CodeExpression);

                    
                    //var arg = new CodeAttributeArgument(GetName(i), GetValue(parametersNotNamed, i) as CodeExpression);
                    lst.Add(arg);
                }

            method.CreateAttribute(t, lst.ToArray());

        }

        public static void CreateAttribute(this MethodMember method, Type type, object parametersNotNamed, object parametersNamed)
        {

            var t = new TypeExp(type);

            List<CodeAttributeArgument> lst = new List<CodeAttributeArgument>();

            if (parametersNotNamed != null)
                foreach (var i in parametersNotNamed.GetType().GetProperties())
                {
                    var arg = new CodeAttributeArgument(GetValue(parametersNotNamed, i) as CodeExpression);
                    lst.Add(arg);
                }

            if (parametersNamed != null)
                foreach (var i in parametersNamed.GetType().GetProperties())
                {
                    var arg = new CodeAttributeArgument(i.Name, GetValue(parametersNotNamed, i) as CodeExpression);
                    lst.Add(arg);
                }

            method.CreateAttribute(t, lst.ToArray());

        }


        private static CodeExpression GetValue(object instance, PropertyInfo i)
        {
            var value = i.GetValue(instance, null);

            if (value == null)
                return Helper.Null;

            if (value is CodeExpression)
                return value as CodeExpression;

            return Helper.Primitive(value);

        }


        #endregion

        #region Constructors

        public static CodeConstructor CreatePublicConstructor(params ParameterExp[] parameters)
        {
            CodeConstructor ctdConstructor = new CodeConstructor();
            if (parameters != null && parameters.Length != 0)
                ctdConstructor.Parameters.AddRange(parameters);

            ctdConstructor.Attributes = MemberAttributes.Public;
            return ctdConstructor;
        }

        public static CodeConstructor CreatePrivateConstructor(params ParameterExp[] parameters)
        {
            CodeConstructor ctdConstructor = new CodeConstructor();
            if (parameters != null && parameters.Length != 0)
                ctdConstructor.Parameters.AddRange(parameters);

            ctdConstructor.Attributes = MemberAttributes.Public;
            return ctdConstructor;
        }

        public static CodeConstructor CreateInternalConstructor(params ParameterExp[] parameters)
        {
            CodeConstructor ctdConstructor = new CodeConstructor();
            
            if (parameters != null && parameters.Length != 0)
                ctdConstructor.Parameters.AddRange(parameters);

            ctdConstructor.Attributes = MemberAttributes.Assembly;
            return ctdConstructor;
        }

        public static void CreatePublicConstructor(this CodeTypeDeclaration Class, params ParameterExp[] parameters)
        {
            Class.Members.Add(CreatePublicConstructor(parameters));
        }

        public static void CreatePrivateConstructor(this CodeTypeDeclaration Class, params ParameterExp[] parameters)
        {
            Class.Members.Add(CreatePrivateConstructor(parameters));
        }

        public static void CreateInternalConstructor(this CodeTypeDeclaration Class, params ParameterExp[] parameters)
        {
            Class.Members.Add(CreateInternalConstructor(parameters));
        }

        #endregion

        #region Class

        public static TypeMember CreateClass(this CodeTypeDeclarationCollection code, string className, bool @public, bool CreateEmptyConstructor)
        {
            var i = CreateClass(className, @public, CreateEmptyConstructor);
            code.Add(i);
            return i;
        }

        public static TypeMember CreateClass(string className, bool @public, bool CreateEmptyConstructor)
        {

            TypeMember Class = new TypeMember(className);

            Class.TypeAttributes = @public ? TypeAttributes.Public : TypeAttributes.NotPublic;

            Class.IsClass = true;

            if (CreateEmptyConstructor)
                Class.CreatePublicConstructor();

            return Class;

        }

        

        public static TypeMember CreateNestedClass(this TypeMember @class, string className, ClassVisibility visibility, bool CreateEmptyConstructor)
        {

            TypeMember Class = new TypeMember(className);

            switch (visibility)
            {
                case ClassVisibility.Private:
                    Class.TypeAttributes = TypeAttributes.NestedPrivate;
                    break;
                case ClassVisibility.Internal:
                    Class.TypeAttributes = TypeAttributes.NestedFamily;
                    break;
                case ClassVisibility.Public:
                    Class.TypeAttributes = TypeAttributes.NestedPublic;
                    break;
                default:
                    break;
            }

            Class.IsClass = true;

            if (CreateEmptyConstructor)
                Class.CreatePublicConstructor();

            @class.Members.Add(Class);

            return Class;

        }

        public static T IsAbstract<T>(this T Class)
            where T : CodeTypeDeclaration
        {
            Class.Attributes |= MemberAttributes.Abstract;
            return Class;
        }

        public static T IsSerializable<T>(this T Class)
            where T : CodeTypeDeclaration
        {
            Class.TypeAttributes |= TypeAttributes.Serializable;
            return Class;
        }

        public static T IsSealed<T>(this T Class)
            where T : CodeTypeDeclaration
        {
            Class.TypeAttributes |= TypeAttributes.Sealed;
            return Class;
        }

        #endregion

    }
}
